Write your first Qiskit Serverless program
Creating utility-scale quantum applications generally requires a variety of compute resource requirements. You can use Qiskit Serverless to easily submit quantum workflows for remote, managed execution on IBM Quantum™ Platform. These quantum workflows can typically be implemented within a common pattern, called a Qiskit pattern. A Qiskit pattern is an intuitive, repeatable set of steps for implementing a quantum computing workflow.
Steps in a Qiskit pattern:
- Map classical inputs to a quantum problem
- Optimize problem for quantum execution
- Execute using Qiskit Runtime primitives
- Post-process, return result in classical format
Once you have built a Qiskit pattern, you can use Qiskit Serverless to deploy and submit it for managed execution.
Example: parallel transpilation with Qiskit Serverless
This example demonstrates how to create a parallel transpilation program and deploy it to IBM Quantum™ Platform to use as a reusable remote service.
Set up the circuits
[2] :from qiskit.circuit.library import (
NLocal,
RXGate,
RYGate,
RZGate,
HGate,
CXGate,
CZGate,
SwapGate,
)
import random
# Function to generate a random rotation gate with a random angle
def random_rotation_gate():
gate_class = random.choice([RXGate, RYGate, RZGate])
theta = random.uniform(0, 2 * 3.14159) # Random angle between 0 and 2π
return gate_class(theta)
# Mapping for single-qubit gates
single_qubit_gates = [random_rotation_gate, HGate]
# Mapping for entangling gates
entangling_gates = [CXGate, CZGate, SwapGate]
# Function to generate a random NLocal circuit
def create_random_nlocal():
num_layers = random.randint(1, 4) # Randomly choose between 1 and 4 layers
rotation_blocks = [
random.choice(single_qubit_gates)() for _ in range(random.randint(1, 3))
]
entanglement_block = random.choice(entangling_gates)()
entanglement = random.choice(
["linear", "full"]
) # Randomly choose the entanglement pattern
nlocal_circuit = NLocal(
num_qubits=20,
rotation_blocks=rotation_blocks,
entanglement_blocks=[entanglement_block],
entanglement=entanglement,
reps=num_layers,
)
return nlocal_circuit
# Generate 30 random NLocal circuits
nlocal_circuits = [create_random_nlocal() for _ in range(30)]nlocal_circuits[0].decompose().draw()Output:
Choose a backend
Your abstract circuits will be optimized to run on this backend.
[6] :# runtime imports
from qiskit_ibm_runtime import QiskitRuntimeService
# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService(channel="ibm_quantum", token=API_TOKEN)
backend = service.least_busy(operational=True, simulator=False)
backend.nameOutput:
'ibm_brisbane'
Set up your local program
Use this local version as a benchmark. Set up your backend and basic transpilation options as follows.
[7] :# Local tranpsilation
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
def transpile_parallel_local(circuit: QuantumCircuit, backend):
"""Transpiles an abstract circuit into an ISA circuit for a given backend."""
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend
)
isa_circuit = pass_manager.run(circuit)
return isa_circuitRun the local version
[ ] :transpile_parallel_local(nlocal_circuits[0], backend)from timeit import default_timer as timer
import warnings
warnings.filterwarnings("ignore")
start = timer()
# run distributed tasks as async function
# we get task references as a return type
tranpsiled_circuits = [
transpile_parallel_local(circuit, backend)
for circuit in nlocal_circuits
]
end = timer()
print(end - start)Output:
683.1403828749899
Deploy to IBM Quantum Platform
Authenticate to the IBMServerlessClient with your IBM Quantum token, which you can find in your IBM Quantum account(opens in a new tab), and upload the script.
from qiskit_serverless import IBMServerlessProvider, QiskitFunction
# Authenticate to the remote cluster and submit the pattern for remote execution
serverless = IBMServerlessProvider(
token=API_TOKEN
)transpile_parallel_demo = QiskitFunction(
title="transpile_parallel",
entrypoint="transpile_parallel.py",
working_dir="./source/",
)serverless.upload(transpile_parallel_demo)Output:
'demo/transpile_parallel'
Run in serverless environment
[ ] :serverless.list()functions = {f.title: f for f in serverless.list()}
transpile_parallel_serverless = functions.get("demo/transpile_parallel")job = transpile_parallel_serverless.run(circuits=nlocal_circuits, backend_name=backend.name)job.status()Output:
'DONE'
logs = job.logs()
for log in logs.splitlines():
print(log)Output:
Starting timer
2024-05-22 20:44:25,433 INFO worker.py:1405 -- Using address 172.17.39.93:6379 set in the environment variable RAY_ADDRESS
2024-05-22 20:44:25,433 INFO worker.py:1540 -- Connecting to existing Ray cluster at address: 172.17.39.93:6379...
2024-05-22 20:44:25,483 INFO worker.py:1715 -- Connected to Ray cluster. View the dashboard at [1m[32m172.17.39.93:8265 [39m[22m
231.3323629554361
result = job.result()circuit = result['transpiled_circuits'][0]
circuit.draw(output='mpl', idle_wires=False)Output:
Add custom dependencies
This example uses the pendulum package as the custom dependency, and will calculate the difference in hours between Toronto and Vancouver timezones.
The following imports your custom dependency, pendulum, and uses its datetime method to calculate date times.
# source_files/pattern_with_dependencies.py
# source_files/program_4.py
from qiskit_serverless import save_result
import pendulum
dt_toronto = pendulum.datetime(2012, 1, 1, tz='America/Toronto')
dt_vancouver = pendulum.datetime(2012, 1, 1, tz='America/Vancouver')
diff = dt_vancouver.diff(dt_toronto).in_hours()
print(diff)
save_result({"hours": diff})Next, create and configure the client.
To install a custom dependency that your pattern might use, pass it as the dependencies argument to the QiskitFunctions class constructor. You can pass multiple dependencies and specify versions.
from qiskit_serverless import QiskitFunction
function = QiskitFunction(
title="pattern-with-dependencies",
entrypoint="pattern_with_dependencies.py",
working_dir="./source_files/",
dependencies=["pendulum==3.0.0"],
)This provider is set up with default credentials to a test cluster intended to run on your machine.
from qiskit_serverless import ServerlessClient
import os
client = ServerlessClient(
token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
clientOutput:
<gateway-client>
client.upload(function)
my_pattern_function = client.get("pattern-with-dependencies")
my_pattern_functionOutput:
QiskitFunction(pattern-with-dependencies)
job = my_pattern_function.run()
jobOutput:
<Job | 5c579361-13e6-4d7c-9fc3-98e0b57ae232>
job.status()Output:
'QUEUED'
job.result()Output:
{'quasi_dists': {'0': 1.0}}
Pass input arguments
Rather than define the circuit inside the pattern, here you can pass it as an argument, and save the results. You can then access them later by calling save_result.
# source_files/pattern_with_arguments.py
from qiskit_serverless import get_arguments, save_result
from qiskit.primitives import Sampler
# get all arguments passed to this pattern
arguments = get_arguments()
# get specific argument that we are interested in
circuit = arguments.get("circuit")
sampler = Sampler()
quasi_dists = sampler.run(circuit).result().quasi_dists
print(f"Quasi distribution: {quasi_dists[0]}")
# saving results of a pattern
save_result({
"quasi_dists": quasi_dists[0]
})Create a circuit that you want to pass as an argument to the pattern.
[ ] :from qiskit import QuantumCircuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()